package cn.skyisazure.metacompare.sqlcompare.compare;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.skyisazure.metacompare.metaVo.DataSourceTypeEnum;
import cn.skyisazure.metacompare.sqlcompare.enums.CompareScopeStrategy;
import cn.skyisazure.metacompare.sqlcompare.enums.DdlExecuteTag;
import cn.skyisazure.metacompare.sqlcompare.exceptions.MetaCompareException;
import cn.skyisazure.metacompare.sqlcompare.meta.*;
import cn.skyisazure.metacompare.sqlcompare.mysql.MysqlMetaConvert;
import cn.skyisazure.metacompare.sqlcompare.mysql.MysqlMetaServiceImpl;
import cn.skyisazure.metacompare.sqlcompare.utils.ChangeSqlModel;
import cn.skyisazure.metacompare.sqlcompare.utils.UnifyUtil;
import lombok.Data;

import java.util.*;
import java.util.stream.Collectors;

@Data
public class CompareUnits {

    private MysqlMetaServiceImpl metaService;

    private List<ChangeSqlModel> changeSqlModels;

    /** 对比策略，粒度细 */
    private enum CompareMode {
        /**啥也不是*/
        NON,
        /**修改*/
        MODIFY,
        /**删除*/
        DROP,
        /**创建*/
        CREATE;
        public static final CompareMode[] ALL = CompareMode.values();
    }

    /**
     * 目的：把<code>target</code>转换为<code>source</code>
     */
    public CompareUnits() {
        changeSqlModels = new ArrayList<ChangeSqlModel>(){
            @Override
            public boolean add(ChangeSqlModel changeSqlModel) {// 优化结构
                String s = changeSqlModel.getSql();
                if (StrUtil.isBlank(s)){return true;}// 空白字符不保存
                if (!s.endsWith(";") && !s.startsWith("/*") && !s.startsWith("-- ")) {s+=";";}// 末尾增加;
                String replace = s.replace(" ;",";").replace("  "," ");// 优化sql结构
                return super.add(changeSqlModel.setSql(replace));
            }
        };
    }

    public List<ChangeSqlModel> compare() {
        return compare(metaService.getConvertToBo().getCompareScopeStrategy());
    }

    public List<ChangeSqlModel> compare(CompareScopeStrategy strategy) {
        switch (strategy) {
            case DATASOURCE:
                CompareMode compareDsMode = compareDataSource();
                if (compareDsMode.equals(CompareMode.DROP)) {
                    break;
                }
                compareTables();
                compareIndices(CompareMode.DROP);
                compareColumns();
                compareIndices(CompareMode.MODIFY, CompareMode.CREATE);
                // compareRelations();
                break;
            case TABLE:
                compareIndices(CompareMode.DROP);
                compareColumns();
                compareIndices(CompareMode.MODIFY, CompareMode.CREATE);
                break;
            default:
                throw new MetaCompareException("不存在策略.");
        }
        return changeSqlModels;
    }

    /**
     * 对比创建库
     * @return 对target库的执行类型
     */
    private CompareMode compareDataSource() {

        DataSourceCM sourceDataSource = metaService.getConvertToBo().getSchemaDataSource("source");
        DataSourceCM targetDataSource = metaService.getConvertToBo().getSchemaDataSource("target");

        CompareMode compareMode;

        if (!targetDataSource.getExistFlag() && sourceDataSource.getExistFlag()) {
            compareMode = CompareMode.CREATE;// 创建库
        } else if (!sourceDataSource.getExistFlag() && targetDataSource.getExistFlag()) {
            compareMode = CompareMode.DROP;// 删除库
        } else {
            compareMode = CompareMode.NON;// 不操作
        }

        if (!sourceDataSource.getBase().equals(DataSourceTypeEnum.OUTSIDE)) {// 排除外部数据源
            if (compareMode.equals(CompareMode.CREATE)) {
                /*内部数据源 创建目标库*/
                changeSqlModels.add(new ChangeSqlModel(metaService.buildCreateDataSourceSql(targetDataSource.getName()),
                        targetDataSource,
                        DdlExecuteTag.CREATE_DATASOURCE));
            } else if (compareMode.equals(CompareMode.DROP)) {
                /*内部数据源 删除目标库*/
                changeSqlModels.add(new ChangeSqlModel(metaService.buildDropDataSourceSql(targetDataSource.getName()),
                        targetDataSource,
                        DdlExecuteTag.DROP_DATASOURCE));
            }
        }
        return compareMode;
    }
    /**
     * 对比表
     */
    private void compareTables() {
        //changeSql.add("/* ===============对比表===================== */");

        // 表名集合
        List<TableCM> sourceTables = metaService.getConvertToBo().getSchemaTables("source");
        List<TableCM> targetTables = metaService.getConvertToBo().getSchemaTables("target");

        // 按数据源Id分组：
        Map<String, List<TableCM>> sourceMapCollect = sourceTables.stream().collect(Collectors.groupingBy(TableCM::getDsId));
        Map<String, List<TableCM>> targetMapCollect = targetTables.stream().collect(Collectors.groupingBy(TableCM::getDsId));

        compareTables(sourceMapCollect,targetMapCollect);
    }
    private void compareTables(Map<String, List<TableCM>> sourceMapCollect,
                               Map<String, List<TableCM>> targetMapCollect) {

        // target数据源已删除的情况，不再对比表

        // 实际 source已经删了，target还有，但是外部数据源不删库，怎么判断当前不需要对比表
        // 1 外部数据源，source无，target有
        // 2 内部数据源，source无，target有

        if (changeSqlModels.stream().anyMatch(sqlModel -> ArrayUtil.contains(sqlModel.getTag(), DdlExecuteTag.DROP_DATASOURCE))) {
            return;
        }

        Set<String> sourceDsIdSet = sourceMapCollect.keySet();
        Set<String> targetDsIdSet = targetMapCollect.keySet();

        // souce特有的表的dsId
        List<String> sourceHas = sourceDsIdSet.stream().filter(sDsId -> {
            return targetDsIdSet.stream().noneMatch(tDsId -> tDsId.equals(sDsId));
        }).collect(Collectors.toList());
        // 两边都有的表的dsId
        List<String> allHas = sourceDsIdSet.stream().filter(sDsId -> {
            return targetDsIdSet.stream().anyMatch(tDsId -> tDsId.equals(sDsId));
        }).collect(Collectors.toList());
        // target特有的表的dsId
        List<String> targetHas = targetDsIdSet.stream().filter(tDsId -> {
            return sourceDsIdSet.stream().noneMatch(sDsId -> sDsId.equals(tDsId));
        }).collect(Collectors.toList());


        targetHas.forEach(dsId-> {
            List<TableCM> tables = targetMapCollect.get(dsId);
            tables.forEach(delTable -> {// target中库中所有表都删除（source中没有表）
                changeSqlModels.add(new ChangeSqlModel(metaService.buildDropTableSql(delTable)));
            });
        });



        allHas.forEach(dsId->{
            List<TableCM> sourceTables = sourceMapCollect.get(dsId);
            List<TableCM> targetTables = targetMapCollect.get(dsId);

            // newTables：需要新增的表 delTables：需要删除的表
            List<TableCM> delTables = targetTables.stream().filter(tTable -> {
                return sourceTables.stream()
                        .filter(sTable -> sTable.getId() != null)
                        .noneMatch(sTable -> sTable.getId().equals(tTable.getId()));
            }).collect(Collectors.toList());

            List<TableCM> newTables = sourceTables.stream().filter(sTable -> {
                return sTable.getId() == null || targetTables.stream().noneMatch(tc-> tc.getId().equals(sTable.getId()));
            }).collect(Collectors.toList());


            // 删除表
            if (CollectionUtil.isNotEmpty(delTables)) {
                delTables.forEach(delTable -> {
                    changeSqlModels.add(new ChangeSqlModel(metaService.buildDropTableSql(delTable)));
                });
            }
            // 新建表
            if (CollectionUtil.isNotEmpty(newTables)) {
                newTables.forEach(newTable -> {
                    changeSqlModels.add(new ChangeSqlModel(metaService.buildCreateTableSql(newTable)));
                });
            }

        });

        sourceHas.forEach(dsId->{
            List<TableCM> tables = sourceMapCollect.get(dsId);
            tables.forEach(newTable -> {// source中库中所有表都新增（target中没有表）
                changeSqlModels.add(new ChangeSqlModel(metaService.buildCreateTableSql(newTable)));
            });
        });

    }


    private void compareColumns() {
        // changeSql.add("/* ===============对比字段===================== */");
        List<ColumnCM> sourceTableColumns = metaService.getConvertToBo().getSchemaColumns("source");
        List<ColumnCM> targetTableColumns = metaService.getConvertToBo().getSchemaColumns("target");

        // 按表Id分组：
        Map<String, List<ColumnCM>> sourceMapCollect = sourceTableColumns.stream().collect(Collectors.groupingBy(ColumnCM::getTableId));
        Map<String, List<ColumnCM>> targetMapCollect = targetTableColumns.stream().collect(Collectors.groupingBy(ColumnCM::getTableId));

        compareColumns(sourceMapCollect,targetMapCollect);
    }

    private void compareColumns(Map<String, List<ColumnCM>> sourceMapCollect,
                                Map<String, List<ColumnCM>> targetMapCollect) {

        Set<String> sourceTableIdSet = sourceMapCollect.keySet();
        Set<String> targetTableIdSet = targetMapCollect.keySet();

        // 两边都有的表Id
        List<String> allHas = sourceTableIdSet.stream().filter(sTableId -> {
            return targetTableIdSet.stream().anyMatch(tTableId -> tTableId.equals(sTableId));
        }).collect(Collectors.toList());


        allHas.forEach(tableId->{
            List<ColumnCM> sourceColumns = sourceMapCollect.get(tableId);
            List<ColumnCM> targetColumns = targetMapCollect.get(tableId);

            // 先增减，再排序
            // modifyColumns：需要修改属性的列 newColumns：需要新增的列 delColumns：需要删除的列
            List<ColumnCM> modifyColumns = sourceColumns.stream().filter(sourceColumn -> sourceColumn.getId() != null).filter(sourceColumn -> {
                Optional<ColumnCM> anyTargetColumn = targetColumns.stream().filter(targetColumn -> sourceColumn.getId().equals(targetColumn.getId()) && !UnifyUtil.columnEq(sourceColumn,targetColumn))// id相同但是属性有变动的
                        .findAny();
                if (anyTargetColumn.isPresent()) {
                    ColumnCM targetColumn = anyTargetColumn.get();
                    // 单行文本不缩短
                    // if (UnifyUtil.columnIsTextShorten(sourceColumn,targetColumn)){
                    //     // 单行文本缩短长度逻辑处理：使用旧(target)的长度
                    //     sourceColumn.setType(targetColumn.getType());
                    // }
                    // 外键不可改为非可空
                    // if (UnifyUtil.columnIsMakeForeignKeyNull(sourceColumn,targetColumn)){
                    //     // 从不可空改为可空逻辑处理：使用旧(target)的值
                    //     sourceColumn.setNullEnableFlag(targetColumn.getNullEnableFlag());
                    // }
                    sourceColumn.setOldName(targetColumn.getName());
                    sourceColumn.setOldType(targetColumn.getType());
                    sourceColumn.setOldNullEnableFlag(targetColumn.getNullEnableFlag());
                    return true;
                }
                return false;
            }).collect(Collectors.toList());
            List<ColumnCM> delColumns = targetColumns.stream().filter(targetColumn -> sourceColumns.stream().filter(sourceColumn -> sourceColumn.getId() != null).noneMatch(sourceColumn -> sourceColumn.getId().equals(targetColumn.getId()))).collect(Collectors.toList());
            List<ColumnCM> newColumns = sourceColumns.stream().filter(sourceColumn -> sourceColumn.getId() == null || targetColumns.stream().noneMatch(tc-> tc.getId().equals(sourceColumn.getId()))).collect(Collectors.toList());


            if (ObjUtil.isNotEmpty(modifyColumns) && !modifyColumns.isEmpty()) {
                modifyColumns.forEach(modifyColumn -> {
                    List<String> sqls = metaService.buildAlterColumnSql(modifyColumn);
                    sqls.forEach(sql -> {
                        changeSqlModels.add(new ChangeSqlModel(sql));
                    });
                });
            }

            if (ObjUtil.isNotEmpty(delColumns) && !delColumns.isEmpty()) {
                delColumns.forEach(delColumn -> {
                    changeSqlModels.add(new ChangeSqlModel(metaService.buildDropColumnSql(delColumn)));
                });
                targetColumns.removeAll(delColumns);
            }
            if (ObjUtil.isNotEmpty(newColumns) && !newColumns.isEmpty()) {
                newColumns.forEach(newColumn -> {
                    newColumn.setOrdinalPosition(0);// 0为了计算ddl方便，因为前端的顺序我不用，前端的赋值会导致计算after重复
                    changeSqlModels.add(new ChangeSqlModel(metaService.buildCreateColumnSql(newColumn)));
                });
                targetColumns.addAll(newColumns);
            }


            // 至此，targetColumns和sourceColumns数据的一样了，后续实现排序：

            // 先给sourceColumns设置OrdinalPosition
            List<ColumnCM> sourceColumnOrders = sourceColumns.stream().peek(sourceColumn -> {
                Optional<ColumnCM> anyTargetColumn = targetColumns.stream().filter(targetColumn -> ObjUtil.isNotEmpty(targetColumn.getId()) && targetColumn.getId().equals(sourceColumn.getId())).findAny();
                if (anyTargetColumn.isPresent()) {
                    sourceColumn.setOrdinalPosition(anyTargetColumn.get().getOrdinalPosition());
                } else {
                    sourceColumn.setOrdinalPosition(0);
                }
            }).collect(Collectors.toList());

            // 检查是否有变动：
            // 如果顺序有变化取变化的sql
            if (!sourceColumnOrders.stream().map(ColumnCM::getOrdinalPosition).equals(targetColumns.stream().map(ColumnCM::getOrdinalPosition))) {
                changeSqlModels.add(new ChangeSqlModel(metaService.buildAlterColumnPosiSql(sourceColumnOrders)));
            }

        });


    }





    /**
     * 对比索引
     * 不用考虑source表无、target表有的情况，因为前面调用比对数据源表的时候会执行删表语句
     */
    private void compareIndices(CompareMode... modes) {
        // changeSql.add("/* ===============对比索引（删索引）===================== */");
        // 删除字段后mysql会联动删除该字段的索引，这种情况再执行删除会报错，所以先[删除索引] -> [增删改字段] -> [修改索引和建索引]
        List<IndexCM> sourceIndices = metaService.getConvertToBo().getSchemaIndices("source");
        List<IndexCM> targetIndices = metaService.getConvertToBo().getSchemaIndices("target");


        // 按表Id分组：
        Map<String, List<IndexCM>> sourceMapCollect = sourceIndices.stream().collect(Collectors.groupingBy(IndexCM::getTableId));
        Map<String, List<IndexCM>> targetMapCollect = targetIndices.stream().collect(Collectors.groupingBy(IndexCM::getTableId));

        compareIndices(sourceMapCollect,targetMapCollect,ArrayUtil.isEmpty(modes)? CompareMode.ALL:modes);
    }


    private void compareIndices(Map<String, List<IndexCM>> sourceMapCollect,
                               Map<String, List<IndexCM>> targetMapCollect,
                               CompareMode... modes) {

        Set<String> sourceTableIdSet = sourceMapCollect.keySet();
        Set<String> targetTableIdSet = targetMapCollect.keySet();

        // souce特有的表Id
        List<String> sourceHas;
        // 两边都有的表Id
        List<String> allHas;
        // target特有的表Id
        List<String> targetHas;


        if (ArrayUtil.contains(modes, CompareMode.DROP)) {
            targetHas = targetTableIdSet.stream().filter(tTableId -> {
                return sourceTableIdSet.stream().noneMatch(sTableId -> sTableId.equals(tTableId));
            }).collect(Collectors.toList());
            targetHas.forEach(tableId -> {
                List<IndexCM> indices = targetMapCollect.get(tableId);
                // 存在表时删索引，表都没有不需要删索引了
                if (metaService.getConvertToBo().existTableId(tableId)) {
                    indices.forEach(delIndex -> {// target中库中所有表都删除（source中没有表）
                        changeSqlModels.add(new ChangeSqlModel(metaService.buildDropIndex(delIndex)));
                    });
                }
            });
        }

        if (ArrayUtil.contains(modes, CompareMode.CREATE)) {
            sourceHas = sourceTableIdSet.stream().filter(sTableId -> {
                return targetTableIdSet.stream().noneMatch(tTableId -> tTableId.equals(sTableId));
            }).collect(Collectors.toList());
            sourceHas.forEach(tableId -> {
                List<IndexCM> indices = sourceMapCollect.get(tableId);
                indices.forEach(newIndex -> {// source中库中所有表都新增（target中没有表）
                    changeSqlModels.add(new ChangeSqlModel(metaService.buildCreateIndex(newIndex),
                            newIndex,
                            DdlExecuteTag.MODIFY_CREATE_INDEX));
                });
            });
        }


        allHas = sourceTableIdSet.stream().filter(sTableId -> {
            return targetTableIdSet.stream().anyMatch(tTableId -> tTableId.equals(sTableId));
        }).collect(Collectors.toList());
        allHas.forEach(tableId -> {
            List<IndexCM> sourceIndices = sourceMapCollect.get(tableId);
            List<IndexCM> targetIndices = targetMapCollect.get(tableId);

            // modifyIndices：需要修改属性的列 newIndices：需要新增的列 delIndices：需要删除的列
            if (ArrayUtil.contains(modes, CompareMode.MODIFY)) {
                List<IndexCM> modifyIndices = sourceIndices.stream().filter(sourceIndex -> sourceIndex.getId() != null).filter(sourceIndex -> {
                    Optional<IndexCM> anyTargetIndex = targetIndices.stream().filter(targetIndex -> sourceIndex.getId().equals(targetIndex.getId()) && !sourceIndex.equals(targetIndex))// id相同但是属性有变动的
                            .findAny();
                    if (anyTargetIndex.isPresent()) {
                        sourceIndex.setOldName(anyTargetIndex.get().getIndexName());
                        return true;
                    }
                    return false;
                }).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(modifyIndices)) {
                    modifyIndices.forEach(modifyIndex -> {
                        changeSqlModels.add(new ChangeSqlModel(metaService.buildAlterIndex(modifyIndex),
                                modifyIndex,
                                DdlExecuteTag.MODIFY_CREATE_INDEX));
                    });
                }
            }
            if (ArrayUtil.contains(modes, CompareMode.DROP)) {
                List<IndexCM> delIndices = targetIndices.stream().filter(targetIndex -> sourceIndices.stream().filter(sourceIndex -> sourceIndex.getId() != null).noneMatch(sourceIndex -> sourceIndex.getId().equals(targetIndex.getId()))).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(delIndices)) {
                    delIndices.forEach(delIndex -> {
                        changeSqlModels.add(new ChangeSqlModel(metaService.buildDropIndex(delIndex)));
                    });
                }
            }
            if (ArrayUtil.contains(modes, CompareMode.CREATE)) {
                List<IndexCM> newIndices = sourceIndices.stream().filter(sourceIndex -> sourceIndex.getId() == null || targetIndices.stream().noneMatch(tc -> tc.getId().equals(sourceIndex.getId()))).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(newIndices)) {
                    newIndices.forEach(newIndex -> {
                        changeSqlModels.add(new ChangeSqlModel(metaService.buildCreateIndex(newIndex),
                                newIndex,
                                DdlExecuteTag.MODIFY_CREATE_INDEX));
                    });
                }
            }

        });


    }


}
